/*
 * Created by JFormDesigner on Mon Jun 29 10:22:22 CST 2020
 */

package mynote;

//import java.awt.*;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * @author Shawn Lou
 */
//软件的主窗体
public class NoteFrame extends JFrame {
    //存储文件的后缀
    private static final String suffix = ".txt";

    //存储查询结果的列表
    private final List<FindInfo> findResults;
    //结果编号
    private int resIdx;

    //文件存放路径
    private final String dataPath;

    //笔记列表model
    private final DefaultListModel<String> listModel;

    //系统剪贴板
    private Clipboard clipboard;

    public NoteFrame(String username) {
        initComponents();
        findResults = new ArrayList<>();
        resIdx = 0;
        clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        dataPath = "src/mynote/data/" + username + "/";
        File dir = new File(dataPath);
        //自动创建用户目录
        if (!dir.exists())
            dir.mkdir();
        listModel = new DefaultListModel<>();
        listNotes.setModel(listModel);
        listNotes.addListSelectionListener(new NoteListListener(this, listNotes));
        btnSave.addActionListener(e -> updateData());
        listNotes.addMouseListener(new PopupListener(popupList));
        txtNote.addMouseListener(new TextPopupListener(popupText,clipboard,itemCopy,itemPaste,txtNote));
        itemDelete.addActionListener(e -> deleteNote());
        itemRename.addActionListener(e -> renameNote());
        itemCopy.addActionListener(e -> copySelected());
        itemPaste.addActionListener(e -> pasteText());
        btnAdd.addActionListener(e -> addNote());
        btnFind.addActionListener(e -> find());
        //如果查询输入变化，清除查询结果
        txtFind.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                clearFindResults();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                clearFindResults();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                clearFindResults();
            }

        });

        initNotes();
        autoSelect();
    }

    private void initComponents() {
        // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
        scrollPane1 = new JScrollPane();
        txtNote = new JTextArea();
        panel1 = new JPanel();
        btnSave = new JButton();
        txtFind = new JTextField();
        btnFind = new JButton();
        scrollPane2 = new JScrollPane();
        listNotes = new JList<>();
        txtTitle = new JTextField();
        btnAdd = new JButton();
        label1 = new JLabel();
        lblLastTime = new JLabel();
        popupList = new JPopupMenu();
        itemRename = new JMenuItem();
        itemDelete = new JMenuItem();
        popupText = new JPopupMenu();
        itemCopy = new JMenuItem();
        itemPaste = new JMenuItem();

        //======== this ========
        setTitle("MyNote");
        setResizable(false);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setIconImage(new ImageIcon(getClass().getResource("/mynote/data/icon.png")).getImage());
        var contentPane = getContentPane();

        //======== scrollPane1 ========
        {

            //---- txtNote ----
            txtNote.setLineWrap(true);
            scrollPane1.setViewportView(txtNote);
        }

        //======== panel1 ========
        {

            //---- btnSave ----
            btnSave.setText("\u4fdd\u5b58");
            btnSave.setFocusPainted(false);
            btnSave.setBackground(SystemColor.activeCaption);

            //---- btnFind ----
            btnFind.setIcon(new ImageIcon(getClass().getResource("/mynote/data/Search.png")));
            btnFind.setBackground(SystemColor.activeCaption);
            btnFind.setFocusPainted(false);
            btnFind.setIconTextGap(0);

            GroupLayout panel1Layout = new GroupLayout(panel1);
            panel1.setLayout(panel1Layout);
            panel1Layout.setHorizontalGroup(
                panel1Layout.createParallelGroup()
                    .addGroup(GroupLayout.Alignment.TRAILING, panel1Layout.createSequentialGroup()
                        .addComponent(txtFind, GroupLayout.PREFERRED_SIZE, 181, GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnFind, GroupLayout.DEFAULT_SIZE, 1, Short.MAX_VALUE)
                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnSave, GroupLayout.PREFERRED_SIZE, 128, GroupLayout.PREFERRED_SIZE))
            );
            panel1Layout.setVerticalGroup(
                panel1Layout.createParallelGroup()
                    .addGroup(panel1Layout.createSequentialGroup()
                        .addGroup(panel1Layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                            .addComponent(btnSave)
                            .addComponent(btnFind)
                            .addComponent(txtFind, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
                        .addGap(0, 0, Short.MAX_VALUE))
            );
        }

        //======== scrollPane2 ========
        {
            scrollPane2.setViewportView(listNotes);
        }

        //---- txtTitle ----
        txtTitle.setEditable(false);

        //---- btnAdd ----
        btnAdd.setText("\u65b0\u5efa");
        btnAdd.setBackground(SystemColor.activeCaption);
        btnAdd.setFocusPainted(false);

        //---- label1 ----
        label1.setText("\u6700\u540e\u66f4\u65b0\u4e8e");
        label1.setForeground(new Color(153, 153, 153));

        //---- lblLastTime ----
        lblLastTime.setForeground(new Color(153, 153, 153));
        lblLastTime.setFont(new Font("NumberOnly", Font.PLAIN, 14));

        GroupLayout contentPaneLayout = new GroupLayout(contentPane);
        contentPane.setLayout(contentPaneLayout);
        contentPaneLayout.setHorizontalGroup(
            contentPaneLayout.createParallelGroup()
                .addGroup(GroupLayout.Alignment.TRAILING, contentPaneLayout.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.TRAILING, false)
                        .addComponent(btnAdd, GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)
                        .addComponent(scrollPane2, GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE))
                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
                        .addComponent(panel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(txtTitle, GroupLayout.Alignment.TRAILING)
                        .addComponent(scrollPane1)
                        .addGroup(contentPaneLayout.createSequentialGroup()
                            .addComponent(label1, GroupLayout.PREFERRED_SIZE, 81, GroupLayout.PREFERRED_SIZE)
                            .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                            .addComponent(lblLastTime, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                    .addContainerGap(15, Short.MAX_VALUE))
        );
        contentPaneLayout.setVerticalGroup(
            contentPaneLayout.createParallelGroup()
                .addGroup(contentPaneLayout.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(contentPaneLayout.createParallelGroup()
                        .addComponent(scrollPane2)
                        .addGroup(contentPaneLayout.createSequentialGroup()
                            .addComponent(txtTitle, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                            .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE)
                            .addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                                .addComponent(lblLastTime)
                                .addComponent(label1))
                            .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                            .addComponent(scrollPane1, GroupLayout.PREFERRED_SIZE, 256, GroupLayout.PREFERRED_SIZE)))
                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(contentPaneLayout.createParallelGroup()
                        .addComponent(btnAdd)
                        .addComponent(panel1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addGap(8, 8, 8))
        );
        pack();
        setLocationRelativeTo(getOwner());

        //======== popupList ========
        {

            //---- itemRename ----
            itemRename.setText("\u91cd\u547d\u540d");
            popupList.add(itemRename);

            //---- itemDelete ----
            itemDelete.setText("\u5220\u9664");
            popupList.add(itemDelete);
        }

        //======== popupText ========
        {

            //---- itemCopy ----
            itemCopy.setText("\u590d\u5236");
            popupText.add(itemCopy);

            //---- itemPaste ----
            itemPaste.setText("\u7c98\u8d34");
            popupText.add(itemPaste);
        }
        // JFormDesigner - End of component initialization  //GEN-END:initComponents
    }

    // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
    private JScrollPane scrollPane1;
    private JTextArea txtNote;
    private JPanel panel1;
    private JButton btnSave;
    private JTextField txtFind;
    private JButton btnFind;
    private JScrollPane scrollPane2;
    private JList<String> listNotes;
    private JTextField txtTitle;
    private JButton btnAdd;
    private JLabel label1;
    private JLabel lblLastTime;
    private JPopupMenu popupList;
    private JMenuItem itemRename;
    private JMenuItem itemDelete;
    private JPopupMenu popupText;
    private JMenuItem itemCopy;
    private JMenuItem itemPaste;
    // JFormDesigner - End of variables declaration  //GEN-END:variables

    //初始化笔记 - 从用户目录读入
    private void initNotes() {
        String filePath = dataPath;
        //List<String> files = new ArrayList<>();
        File file = new File(filePath);
        File[] subList = file.listFiles();
        String fileName;
        if (subList == null)
            return;
        for (File f:
             subList) {
            fileName = f.getName();
            listModel.addElement(fileName.substring(0,fileName.lastIndexOf(".")));
        }
    }

    //显示笔记到文本框
    private void showNote(String title, String note) {
        txtTitle.setText(title);
        txtNote.setText(note);
    }

    //选择列表项，获取并显示笔记内容
    public void getNote(String title) {
        String fileName = title + suffix;
        String encoding = "UTF-8";
        File file = new File(dataPath + fileName);
        long fileLength = file.length();
        byte[] fileBytes = new byte[(int) fileLength];
        String fileContent;
        try {
            FileInputStream fis = new FileInputStream(file);
            fis.read(fileBytes);
            fis.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        try {
            fileContent = new String(fileBytes, encoding);
            showNote(title, fileContent);
        }
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        updateLastModTime(file);
    }

    //自动选择列表第一项
    private void autoSelect() {
        listNotes.setSelectedIndex(0);
    }

    //删除笔记
    public void deleteNote() {
        String name = listNotes.getSelectedValue();
        listModel.removeElementAt(listNotes.getSelectedIndex());
        File file = new File(dataPath + name + suffix);
        if (file.exists())
            file.delete();
        autoSelect();
    }

    //重命名笔记
    public void renameNote() {
        String oldName = listNotes.getSelectedValue();
        String name = JOptionPane.showInputDialog(null,"新标题：", "", JOptionPane.PLAIN_MESSAGE);
        if (name == null)
            return;
        //空字符串
        if (name.isEmpty()) {
            JOptionPane.showConfirmDialog(null,"标题不可为空！",
                    "重命名失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }
        //检测特殊字符
        if (illegalName(name)) {
            JOptionPane.showConfirmDialog(null,"标题不可包含特殊字符！",
                    "重命名失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }
        File oldFile = new File(dataPath + oldName + suffix);
        File newFile = new File(dataPath + name + suffix);
        //存在同名文件
        if (newFile.exists()) {
            JOptionPane.showConfirmDialog(null,"已存在同名笔记！",
                    "重命名失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }
        if (oldFile.exists() && oldFile.isFile()) {
            oldFile.renameTo(newFile);
        }
        listModel.set(listNotes.getSelectedIndex(),name);
        txtTitle.setText(name);
        listNotes.updateUI();
        updateLastModTime(newFile);
    }

    //保存笔记更改至文件
    public void updateData() {
        //获取标题，标题不可为空
        //笔记由标题唯一标识
        String title = listNotes.getSelectedValue();
        if (title.isEmpty()) {
            JOptionPane.showConfirmDialog(null,"标题不可为空！",
                    "保存失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }

        //获取笔记内容
        String note = txtNote.getText();

        File file = new File(dataPath + title + suffix);
        if (!file.exists())
            listModel.addElement(title);
        writeFile(file, note);
        updateLastModTime(file);
        clearFindResults();
    }

    //新建笔记
    public void addNote() {
        String name = JOptionPane.showInputDialog(null,"标题：", "新建笔记", JOptionPane.PLAIN_MESSAGE);
        if (name == null)
            return;
        if (name.equals("")) {
            JOptionPane.showConfirmDialog(null,"标题不可为空！",
                    "创建失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }
        if (illegalName(name)) {
            JOptionPane.showConfirmDialog(null,"标题不可包含特殊字符！",
                    "创建失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }
        File file = new File(dataPath + name + suffix);
        if (file.exists()) {
            JOptionPane.showConfirmDialog(null,"标题不可重名！",
                    "创建失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
            return;
        }


        listModel.addElement(name);
        writeFile(file, "");
        listNotes.setSelectedValue(name, true);

    }

    //复制选中文字
    public void copySelected() {
        String text = txtNote.getSelectedText();
        Transferable tran = new StringSelection(text);
        clipboard.setContents(tran, null);
    }

    //粘贴剪贴板内容
    public void pasteText() {
        Transferable tran = clipboard.getContents(null);
        if (tran != null && tran.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            try {
                String text = (String)tran.getTransferData(DataFlavor.stringFlavor);
                txtNote.insert(text, txtNote.getCaretPosition());
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //清除查找结果列表
    public void clearFindResults() {
        if (!findResults.isEmpty())
            findResults.clear();
    }

    //查找
    public void find() {
        //获取查询内容
        String text = txtFind.getText();
        //若查询结果列表为空，开始查询
        if (findResults.isEmpty()) {
            resIdx = 0;
            //查询内容为空
            if (text.isEmpty()) {
                JOptionPane.showConfirmDialog(null,"搜索内容为空！",
                        "查找失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
                return;
            }
            //查询操作未能找到结果
            if (!findText(text)) {
                JOptionPane.showConfirmDialog(null,"找不到笔记！",
                        "查找失败",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
                return;
            }
        }
        //查询结果列表不为空，从中获取结果
        FindInfo res = findResults.get(resIdx);
        //下一次获取下一个结果
        resIdx++;
        if (resIdx >= findResults.size())
            resIdx = 0;
        //获取标题
        String name = res.fName;
        name = name.substring(0,name.lastIndexOf("."));
        int idx = res.idx;
        //定位至结果位置
        listNotes.setSelectedValue(name, true);
        txtNote.requestFocus();
        txtNote.setSelectionStart(idx);
        txtNote.setSelectionEnd(idx + text.length());
    }

    /**
     * 遍历所有笔记查找内容
     * @return true:找到符合条件内容 false:未找到符合条件内容
     */
    public boolean findText(String text) {
        boolean rs = false;
        String filePath = dataPath;
        String encoding = "UTF-8";
        File file = new File(filePath);
        File[] subList = file.listFiles();
        FileInputStream fis = null;
        byte[] fileBytes;
        String fileContent;
        if (subList == null)
            return rs;
        for (File f:
                subList) {
            try {
                fileBytes = new byte[(int) f.length()];
                fis = new FileInputStream(f);
                fis.read(fileBytes);
                fileContent = new String(fileBytes, encoding);
                fis.close();
                int idx = fileContent.indexOf(text);
                while (idx != -1) {
                    findResults.add(new FindInfo(f.getName(), idx));
                    //继续在当前文件查找下一个
                    idx = fileContent.indexOf(text, idx+1);
                    rs = true;
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return rs;
    }

    /**
     * @param file 文件
     * @param content 写入内容
     */
    private void writeFile(File file, String content) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(content.getBytes());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                fos.flush();
                fos.close();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //更新“最后更新时间”
    private void updateLastModTime(File file) {
        long time = file.lastModified();
        Calendar car = Calendar.getInstance();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        car.setTimeInMillis(time);
        lblLastTime.setText(formatter.format(car.getTime()));
    }

    /**
     * @return true 标题合法 false 存在特殊字符
     */
    private boolean illegalName(String name) {
        return name.matches(".*[\\\\/:*?\"<>\\|]+.*");
    }
}

// 搜索结果信息类
class FindInfo {
    public String fName;
    public int idx;

    public FindInfo(String name, int s) {
        this.fName = name;
        this.idx = s;
    }
}